-module(state_storage).
-include("definitions.hrl").

-export([start/0, stop/0, update_state/3, flush/0, init/0, get_current_states/0, get_last_for_device/1, clear/0, try_reboot/0]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                   интерфейсные
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec start/0 :: () -> true.
start() ->
  {Pid, _} = spawn_monitor(state_storage, init, []),
  true = register(state_storage, Pid).

-spec try_reboot/0 :: () -> boolean().
try_reboot() ->
  case whereis(state_storage) of
    undefined -> false;
    Pid when is_pid(Pid) ->
      Pid ! reboot
  end
%%   try state_storage ! reboot of
%%     reboot -> true
%%   catch _:_ -> false
%%   end.
.

-spec update_state/3 :: (atom(), state(), pid()) -> ok.
update_state(Atom, State, WaiterPid) ->
  case whereis(Atom) of
    undefined ->
      error(Atom);
%%       error(Atom"stateStorage is undefined at updateState()");
    Pid ->
      Pid ! {update, State, WaiterPid}
  end,
  ok.

-spec get_current_states/0 :: () -> [state()].
get_current_states() ->
  case whereis(state_storage) of
    undefined ->
      error("get_current_states: state_storage undefined");
    Pid -> Pid ! {getCurrentState, self()},
      receive
        {states, States} when is_list(States) ->
          States
      end
  end.

-spec get_last_for_device/1 :: (pos_integer()) -> state().
get_last_for_device(DeviceId) ->
  case whereis(state_storage) of
    undefined ->
      io:format("state_storage finished~n"),
      ok;
    Pid -> Pid ! {getLastState, DeviceId, self()},
      receive
        {last, State} ->
          State
      end
  end
.
-spec stop/0 :: () -> ok.
stop() ->
  case whereis(state_storage) of
    undefined -> ok;
    Pid -> Pid ! {stop, self()},
      receive
        state_storage_stopped -> ok
      end,
      io:format("state storage finished~n"),
      ok
  end.

-spec clear/0 :: () -> ok.
clear() ->
  case whereis(state_storage) of
    undefined ->
      error("clear when state storage finished~n");
    Pid when is_pid(Pid) -> Pid ! {clear, self()},
      receive
        clean -> ok
      end
  end.

%неблокирующая
% а стала блокирующая
-spec flush/0 :: () -> ok.
flush() ->
  state_storage ! {flush, self()},
  receive
    {ok, flushed} -> ok
  end.

-spec event_loop/3 :: (ets:tab(), non_neg_integer(), can_die | can_not_die) -> state_storage_stopped.

%%  {_, Size} = erlang:process_info(self(), message_queue_len),
event_loop(Tab, UnsavedCount, MyState) ->
  receive
    {stop, Pid} ->
      ok = flush_to_storage(),
      Pid ! state_storage_stopped;

    {clear, From} when is_pid(From) ->
%%       io:format("clear~n"),
      clear(Tab),
      From ! clean,
      event_loop(Tab, 0, MyState);

    {flush, From} when is_pid(From) ->
%%       io:format("flush~n"),
      ok = flush_to_storage(),
      From ! {ok, flushed},
      event_loop(Tab, 0, MyState);

    {update, State, WaiterPid} when is_pid(WaiterPid) ->
%%       io:format("update~n"),
      update_state(Tab, UnsavedCount, MyState, State, WaiterPid);

    {getCurrentState, From} ->
%%       {_, Size} = erlang:process_info(self(), message_queue_len),
%%       io:format("getCurrentState ~p size= ~p~n", [MyState, Size]),
      States = get_all(),
      From ! {states, States},
      event_loop(Tab, UnsavedCount, MyState);

    {getLastState, DeviceId, Pid} when is_pid(Pid), is_integer(DeviceId) ->
%%       io:format("getLastState~n"),
      State = get_last_state(DeviceId),
      Pid ! {last, State},
      event_loop(Tab, UnsavedCount, MyState);

    reboot ->
%%       io:format("reboot~n"),
      case MyState of
        can_die -> die(Tab, UnsavedCount, MyState);
        can_not_die -> event_loop(Tab, UnsavedCount, MyState)
      end
  end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                   реализация
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec init/0 :: () -> state_storage_stopped.
init() ->
  Tab = ets:new(stateTable, [named_table, {keypos, #stateRecord.deviceId}]),
%%   io:format("state_storage started~n"),
  event_loop(Tab, 0, can_not_die).

-spec clear/1 :: (ets:tab()) -> true.
clear(Tab) ->
  ets:delete_all_objects(Tab).

-spec flush_to_storage/0 :: () -> ok.
flush_to_storage() ->
  States = get_all(),
  flush_items(States).

-spec flush_items/1 :: ([state()]) -> ok.
flush_items([]) ->
  ok;

flush_items([State | States]) ->
  storage:update_state(State),
  flush_items(States).

-spec get_all/0 :: () -> [state()].
get_all() ->
  lists:flatten(ets:match(stateTable, '$1')).

-spec get_last_state/1 :: (pos_integer()) -> state().
get_last_state(DeviceId) ->
  State = ets:lookup(stateTable, DeviceId),
  case State of
    [] ->
%%       io:format("state_storage: getLastState: State was not restored after fall dev ~p~n", [DeviceId]),
      storage:get_last_state_for_device(DeviceId);
    List when is_list(List) ->
      hd(List)
  end.

die(Tab, Unsaved, MyState) ->

  {_, Size} = erlang:process_info(self(), message_queue_len),
%%   io:format("Queue ~p size = ~p~n", [MyState, Size]),
  if
    Size > 0 ->
%%       self() ! reboot,
      event_loop(Tab, Unsaved, MyState);

    true ->
      ok = if
             Unsaved > 0 ->
               flush_to_storage();
             true -> ok
           end,
      clear(Tab),
      flush_msgs(),
      garbage_collect(self()),
%%       unregister(state_storage),
      exit(self(), normal)
  end.

flush_msgs() ->
  receive
    Any ->
      io:format("~p flush_msgs received: ~p~n", [?MODULE, Any]),
      flush_msgs()
  after
    0 ->
      true
  end.

update_state(Tab, UnsavedCount, MyState, State, WaiterPid) ->
%%   if
%%     State#stateRecord.completed ->
%%       io:format("state_storage:update_state/5 completed: dev = ~p~n", [State#stateRecord.deviceId]);
%%     true -> ok
%%   end,
  ets:insert(stateTable, State),
  case ?BENCHMARK of
    false ->
      ok;
    _ ->
      WaiterPid ! {ok, updatedState}
  end,
  {ok, BufferSize} = application:get_env(state_storage_buffer_size),
  Unsaved = if
              UnsavedCount > BufferSize ->
                ok = flush_to_storage(),
                0;
              true ->
                UnsavedCount + 1
            end,

  case MyState of
    can_die ->
      event_loop(Tab, Unsaved, MyState);

    can_not_die ->
      {memory, MemoryAllocated} = erlang:process_info(self(), memory),
      %%       io:format("state_storage memory ~p~n", [MemoryAllocated]),
      {ok, MemoryLimit} = application:get_env(state_storage_max_memory),
      NextState = if
                    MemoryAllocated > MemoryLimit ->
                      {_, Size} = erlang:process_info(self(), message_queue_len),
                      storage_supervisor:shift(),
                      receive
                        get_ready ->
                          io:format("get_ready ~p~n", [Size]),
                          can_die
                      end;

                    true ->
                      can_not_die
                  end,

      event_loop(Tab, Unsaved, NextState)
  end.